dojo.require('dojo.fx');

// our options object
var options = {};

function collapsibleContainer(options) {

  this.domNode = dojo.create('div',
    { 'class': 'collapsibleContainerNode' });

  // the div that will show up at the top of the container, and will
  // allow the container to be opened
  this.containerTitleNode = dojo.create('div',
    { 'class': 'collapsibleContainerTitleNode' });
  this.domNode.appendChild(this.containerTitleNode);

  // the arrow that indicates whether the container is opened or closed
  this.containerOpener = dojo.create('span',
    { 'class': 'collapsibleContainerOpened' });
  this.containerTitleNode.appendChild(this.containerOpener);

  // which node will open/close the container when clicked?
  var containerTarget = this.containerOpener;
  if (options && options.target == 'titlebar') {
      containerTarget = this.containerTitleNode;
  }
  this.openerLink = dojo.connect(containerTarget, 'onclick',
    this, this.viewChanger);

  // the content shown for the container title
  this.containerTitleSpan = dojo.create('span',
    { 'class': 'collapsibleContainerTitleSpan' });
  this.containerTitleNode.appendChild(this.containerTitleSpan);
  if (options && options.title) {
    this.containerTitleSpan.innerHTML = options.title;
  }

  // any content on the right of the title bar
  this.containerTitleRightSpan = dojo.create('span',
    { 'class': 'collapsibleContainerRightSpan' });
  this.containerTitleNode.appendChild(this.containerTitleRightSpan);

  var clearDiv = dojo.create('div',
    { 'style': 'clear: both' });
  this.containerTitleNode.appendChild(clearDiv);

  // the content that gets hidden
  this.containerContentStrut = dojo.create('div',
    { 'class': 'collapsibleContainerStrut' });
  this.domNode.appendChild(this.containerContentStrut);
  this.containerContentDiv = dojo.create('div',
    { 'class': 'collapsibleContainerContent' });
  this.containerContentStrut.appendChild(this.containerContentDiv);
  if (options && options.content) {
    this.containerContentDiv.innerHTML = options.content;
  }

  // set up the toggler
  this.containerOpened = true;
  this.containerToggler = new dojo.fx.Toggler({
    node: this.containerContentStrut,
    showFunc: dojo.fx.wipeIn, hideFunc: dojo.fx.wipeOut });

  if (options && options.startClosed == true) {
      this.viewChanger();
  }
}

collapsibleContainer.prototype.viewChanger = function(evtObj) {
  if (this.containerOpened == false) {
    this.containerToggler.show();
    dojo.addClass(this.containerOpener, 'collapsibleContainerOpened');
    dojo.removeClass(this.containerOpener, 'collapsibleContainerClosed');
    this.containerOpened = true;
  } else {
    this.containerToggler.hide();
    dojo.addClass(this.containerOpener, 'collapsibleContainerClosed');
    dojo.removeClass(this.containerOpener, 'collapsibleContainerOpened');
    this.containerOpened = false;
  }
};

function sigFigs(val, unc) {
    var uncprec = /^([0\.]*.)(.*)$/.exec('' + unc);

    var uncvalid = uncprec[1];
    var uncinvalid = uncprec[2];

    uncinvalid.replace(/[^\.]/,'0');
    var newuncprec = uncvalid + uncinvalid;

    var thisprec = 0;
    if (/^0\./.test(newuncprec)) {
	for (var i = 0; i < newuncprec.length; i++) {
	    thisprec++;
	    if (newuncprec.substring(i, 1) == '.') {
		thisprec = 0;
	    } else if (newuncprec.substring(i, 1) != '0') {
		break;
	    }
	}
	thisprec *= -1;
    } else {
	for (var i = 0; i < newuncprec.length; i++) {
	    thisprec++;
	    if (newuncprec.substring(i, 1) != '0') {
		thisprec = 0;
	    }
	}
    }

    var newval = Math.floor(val / Math.pow(10, thisprec)) *
	Math.pow(10, thisprec);
    var newunc = Math.floor(unc / Math.pow(10, thisprec)) *
	Math.pow(10, thisprec);

    var output = newval + ' +/- ' + newunc;

    return output;
}

function tagValue(args) {
  if ((!args) || (!args.tag) || (!args.string)) {
    return (null);
  }

  var elementsExp;
  if (args.string.search(/\&/) != -1) {
    elementsExp = new RegExp('^(.*?)' + args.tag + '\\=(\\S+?)\\&(.*)$', '');
  } else {
    elementsExp = new RegExp('^(.*?)' + args.tag + '\\=(\\S+?)$','');
  }
  var elements = elementsExp.exec(args.string);
  var value = elements[2];
  var remainder = elements[1] + elements[3];

  return ({ value: value, remainder: remainder });
}

function nextToken(string) {
    // find the string directly after the next question mark (?)
    var elements = /^\?*(\S+?)\=.*$/.exec(string);
    if (!elements) {
	return (null);
    }
    return (elements[1]);
}

function drawCalTablePage(data, ioargs) {
    var DOMnode = dojo.byId('calTableDiv');

    var calTableDOM = dojo.create('div');

    calTableDOM.appendChild(dojo.create('br'));
    calTableDOM.appendChild(dojo.create('br'));

    var warning = dojo.create('div');
    calTableDOM.appendChild(warning);

    if (!data || !data.calibrators ||
	data.calibrators.length == 0) {
	warning.appendChild(dojo.create('h2', {
		    innerHTML: 'No sources found in this range!' }));
	dojo.empty(DOMnode);
	DOMnode.appendChild(calTableDOM);
	return;
    }

    warning.appendChild(dojo.create('b', {
		innerHTML: 'Note:' }));
    warning.innerHTML += ' The presence of a source in this table does ' +
	'not necessarily mean it is a good calibrator for your purposes. ' +
	'Please click on the link of a prospective calibrator to check ' +
	'its suitability.';
    
    calTableDOM.appendChild(dojo.create('br'));
    calTableDOM.appendChild(dojo.create('br'));
    var calTable = dojo.create('table', {
	    'class': 'calTable' });
    calTableDOM.appendChild(calTable);
    var bandNames = ['16cm', '6cm', '3cm', '15mm', '7mm', '3mm'];
    var precedingColumns;
    var precedingNames;
    if (data.calibrators[0].angdist) {
	precedingColumns = ['Distance (deg)', 'Source', 'RA', 'Dec'];
	precedingNames = ['angdist', 'name', 'ra', 'dec'];
    } else {
	precedingColumns = ['Source', 'RA', 'Dec'];
	precedingNames = ['name', 'ra', 'dec'];
    }

    var tH = dojo.create('tr');
    calTable.appendChild(tH);
    for (var i = 0; i < precedingColumns.length; i++) {
	var tC = dojo.create('th', {
		innerHTML: precedingColumns[i] });
	tH.appendChild(tC);
    }
    for (var i = 0; i < bandNames.length; i++) {
	var tC = dojo.create('th', {
		innerHTML: bandNames[i] + ' flux (Jy)' });
	tH.appendChild(tC);
    }
    
    for (var i = 0; i < data.calibrators.length; i++) {
	var tR = dojo.create('tr');
	calTable.appendChild(tR);
	
	for (var j = 0; j < precedingColumns.length; j++) {
	    var tC;
	    if (precedingColumns[j] == 'Source'){
		tC = dojo.create('td');
		tC.appendChild(dojo.create('a', {
			    href: 'http://www.narrabri.atnf.csiro.au/' +
				'calibrators/newdb/calview.html?' +
				'source=' +
				data.calibrators[i][precedingNames[j]],
				innerHTML:
			    data.calibrators[i][precedingNames[j]] }));
	    } else {
		tC = dojo.create('td', {
		    innerHTML: data.calibrators[i][precedingNames[j]] });
	    }
	    if (i % 2) {
		dojo.addClass(tC, 'secondRow');
	    }
	    tR.appendChild(tC);
	}
	
	for (var j = 0; j < bandNames.length; j++) {
	    var tC = dojo.create('td');
	    tR.appendChild(tC);
	    if (data.calibrators[i].fluxes[bandNames[j]]){
		if (dojo.isIE){
		    tC.innerText =
			data.calibrators[i].fluxes[bandNames[j]];
		} else {
		    tC.innerHTML = 
			data.calibrators[i].fluxes[bandNames[j]];
		}
	    }
	    if (i % 2) {
		dojo.addClass(tC, 'secondRow');
	    }
	}
    }

    dojo.empty(DOMnode);
    DOMnode.appendChild(calTableDOM);
}

function drawCalViewMeasurements(data, ioargs) {
    var DOMnode = dojo.byId('observationSummary');


    if ((!data.measurements)||
	(data.measurements.length == 0)) {
	dojo.empty(DOMnode);
	DOMnode.innerHTML = '<h2>No measurements found!</h2>';
	return;
    }

    // which frequencies are present?
    var measureFreqs = [];
    var measureBands = [];
    for (var i = 0; i < data.measurements.length; i++) {
	var tFreq = data.measurements[i].frequency;
	var tBand = data.measurements[i].band;
	if (((options.short == 'true') ||
	     (options.recommended == 'true')) &&
	    (data.measurements[i].recommended != 'true')) {
	    continue;
	}
	if (options.frequencies) {
	    var found = false;
	    for (var j = 0; j < options.frequencies.length; j++) {
		if (options.frequencies[j] ==
		    data.measurements[i].frequency) {
		    found = true;
		    break;
		}
	    }
	    if (found == false) {
		continue;
	    }
	}
	if (options.bands) {
	    var found = false;
	    for (var j = 0; j < options.bands.length; j++) {
		if (options.bands[j] ==
		    data.measurements[i].band) {
		    found = true;
		    break;
		}
	    }
	    if (found == false) {
		continue;
	    }
	}
	var tAdded = false;
	for (var j = 0; j < measureFreqs.length; j++) {
	    if (measureFreqs[j] == tFreq) {
		tAdded = true;
		break;
	    }
	}
	if (tAdded == false) {
	    measureFreqs.push(tFreq);
	}
	tAdded = false;
	for (var j = 0; j < measureBands.length; j++) {
	    if (measureBands[j] == tBand) {
		tAdded = true;
		break;
	    }
	}
	if (tAdded == false) {
	    measureBands.push(tBand);
	}
    }

    // sort in ascending order
    measureFreqs.sort(function(a, b) {
	    return a - b });
    
    // now go through and make the table
    var bandContainers = {};
    var tableHeadings = ['Frequency (MHz)', 'Obs Time', 'Flux Density (Jy)',
			 '# Obs', 'Defect','Info'];
    var tableElements = ['frequency', 'date', 'fluxdensity',
			 'nobs', 'defects', 'info'];
    var defectHeadings = ['6km', '1.5km', '750m', '<375m'];
    var defectBaselines = [6000, 1500, 750, 375];
    for (var i = 0; i < measureBands.length; i++) {
	bandContainers[measureBands[i]] =
	    new collapsibleContainer({
		    target: 'titlebar',
		    title: measureBands[i] });

	bandContainers[measureBands[i]].bandTable =
	    dojo.create('table', {
		'class': 'bandTable' });
	bandContainers[measureBands[i]].containerContentDiv.
	    appendChild(bandContainers[measureBands[i]].bandTable);
	// warn users about confusion in the 16cm band
	if (measureBands[i] == '16cm') {
	    var warning = dojo.create('p');
	    bandContainers[measureBands[i]].containerContentDiv.
		appendChild(warning);
	    warning.appendChild(dojo.create('b', {
			innerHTML: 'Warning for 16cm observers:' }));
	    warning.innerHTML += ' A typical 20cm field will contain a ' +
		'100 mJy confusing source. In very compact arrays, ' +
		'300 mJy of confusing flux is common on the shortest ' +
		'spacings. At 16cm, calibrators stronger than 5 Jy ' +
		'are recommended for ATCA arrays shorter than 375m.';
	}

	var tableHeadRow = dojo.create('tr');
	var tableHeadSub = dojo.create('tr');
	bandContainers[measureBands[i]].bandTable.
	    appendChild(tableHeadRow);
	bandContainers[measureBands[i]].bandTable.
	    appendChild(tableHeadSub);
	for (var j = 0; j < tableHeadings.length; j++) {
	    var rowSpan = 2;
	    var colSpan = 1;
	    if (tableHeadings[j] == 'Defect') {
		rowSpan = 1;
		colSpan = defectHeadings.length;
		for (var k = 0; k < defectHeadings.length; k++) {
		    var headSub = dojo.create('th', {
			    innerHTML: defectHeadings[k] });
		    tableHeadSub.appendChild(headSub);
		}
	    }
	    var headCell = dojo.create('th', {
		    rowspan: rowSpan, colspan: colSpan,
		    innerHTML: tableHeadings[j] });
	    tableHeadRow.appendChild(headCell);
	}
    }

    // fill the table
    var tableRows = {};
    for (var i = 0; i < measureFreqs.length; i++) {
	for (var j = 0; j < data.measurements.length; j++) {
	    if (data.measurements[j].frequency == measureFreqs[i]) {
		var dN = '' + data.measurements[j].frequency;
		if (!tableRows[dN]) {
		    tableRows[dN] = {};
		    tableRows[dN].frequency =
			data.measurements[j].frequency;
		    tableRows[dN].band =
			data.measurements[j].band;
		    tableRows[dN].recommended =
			data.measurements[j].recommended;
		    tableRows[dN].defects = [];
		    for (var k = 0; k < defectHeadings.length; k++) {
			tableRows[dN].defects[k] = '';
			if (data.measurements[j].baseline ==
			    defectBaselines[k]) {
			    tableRows[dN].defects[k] =
				Math.floor(data.measurements[j].defect)
				+ '%';
			}
		    }
		    tableRows[dN].mjd =
			data.measurements[j].obsmjd;
		    tableRows[dN].date =
			data.measurements[j].obsdate;
		    tableRows[dN].fluxdensity =
			sprintf("%7s +/- %5s", '' +
				data.measurements[j].obsfluxdensity,
				'' +
				data.measurements[j].obsfluxunc);
		    tableRows[dN].nobs =
			data.measurements[j].nobs;
		    tableRows[dN].info = dojo.create('span');
		    if (data.measurements[j].plots &&
			((data.measurements[j].plots.uvdist) ||
			(data.measurements[j].plots.ucvc))) {
			var historyLink = dojo.create('a', {
				target: 'historyplots',
				href: 'calplot.html?source=' +
				options.source + '&frequency=' +
				data.measurements[j].frequency,
				innerHTML: 'Flux History   ' });
			var plotLink = dojo.create('a', {
				target: 'visplots',
				href: 'calplot.html?source=' +
				options.source + '&band=' +
				data.measurements[j].band,
				innerHTML: 'Visibility Plots' });
			tableRows[dN].info.appendChild(historyLink);
			tableRows[dN].info.appendChild(plotLink);
		    }
		    continue;
		}
		for (var k = 0; k < defectHeadings.length; k++) {
		    tableRows[dN].defects[k] = '';
		    if (data.measurements[j].baseline ==
			defectBaselines[k]) {
			tableRows[dN].defects[k] =
			    Math.floor(data.measurements[j].defect)
			    + '%';
		    }
		}
		tableRows[dN].nobs +=
		    data.measurements[j].nobs;
		if (data.measurements[j].obsmjd >
		    tableRows[dN].mjd) {
		    tableRows[dN].mjd =
			data.measurements[j].obsmjd;
		    tableRows[dN].date =
			data.measurements[j].obsdate;
		    tableRows[dN].fluxdensity =
			sprintf("%7s +/- %5s", '' +
				data.measurements[j].obsfluxdensity,
				'' +
				data.measurements[j].obsfluxunc);
		}
	    }
	}
    }

    for (var x in tableRows) {
	// put this row in the right container
	var tR = dojo.create('tr');
	var bC = bandContainers[tableRows[x].band].bandTable;
	bC.appendChild(tR);

	for (var i = 0; i < tableHeadings.length; i++) {
	    if (tableHeadings[i] == 'Defect'){
		for (var j = 0; j < defectBaselines.length; j++) {
		    var tD = dojo.create('td', {
			    innerHTML:
			    tableRows[x][tableElements[i]][j] });
		    tR.appendChild(tD);
		}
	    } else if (tableHeadings[i] == 'Info'){
		var tD = dojo.create('td');
		tR.appendChild(tD);
		tD.appendChild(tableRows[x][tableElements[i]]);
	    } else {
		var tD = dojo.create('td', {
			innerHTML: tableRows[x][tableElements[i]] });
		tR.appendChild(tD);
	    }
	    if (tableRows[x].recommended == 'true'){
		dojo.addClass(tR, 'recommendedFreq');
	    }
	}
    }

    dojo.empty(DOMnode);
    if (!measureBands || measureBands.length == 0) {
	DOMnode.appendChild(dojo.create('h2', {
		    innerHTML: 'No measurements found!' }));
    } else {
	for (var i = 0; i < measureBands.length; i++) {
	    DOMnode.appendChild(bandContainers[measureBands[i]].domNode);
	}
    }
}

function drawCalViewPage(data, ioargs) {
    var DOMnode = dojo.byId('contentDiv');

    if (data.name != options.source) {
	DOMnode.appendChild(dojo.create('h2', {
		    innerHTML: 'Source name not recognised as ' +
			'a calibrator!' }));
	return;
    }
    
    var sourceInfoDiv = dojo.create('div', {
	    'class': 'sourceInfo' });
    DOMnode.appendChild(sourceInfoDiv);

    sourceInfoDiv.appendChild(dojo.create('h2', {
		innerHTML: 'General source information' }));
    var infoList = dojo.create('ul');
    sourceInfoDiv.appendChild(infoList);
    var raDecItem = dojo.create('li', {
	    innerHTML: 'RA, Dec: ' + data.ra + ',' + data.dec +
	    ' (J2000)' });
    infoList.appendChild(raDecItem);

    // get the source rise/set LST
    var sCI = new AstroCoord();
    sCI.addSource({ name: data.name, ra: data.ra, dec: data.dec });
    var sourceSets = sCI.get_info(data.name);
    var sourceRiseLST = sourceSets.ra_turns - sourceSets.haset;
    while (sourceRiseLST >= 1) {
	sourceRiseLST -= 1;
    }
    while (sourceRiseLST < 0) {
	sourceRiseLST += 1;
    }
    var sourceSetLST = sourceSets.ra_turns + sourceSets.haset;
    while (sourceSetLST >= 1) {
	sourceSetLST -= 1;
    }
    while (sourceSetLST < 0) {
	sourceSetLST += 1;
    }
    var setLSTItem;
    if (sourceSets.haset != -99.0 && sourceSets.haset != 99.0) {
	var sRT = sCI.turns2str(sourceRiseLST, 'degrees', 'coordinateHours');
	var nRT = sRT.replace(/\.(.*)$/, '');
	var nRT = nRT.replace(/\+/g,'');
	var sST = sCI.turns2str(sourceSetLST, 'degrees', 'coordinateHours');
	var nST = sST.replace(/\.(.*)$/, '');
	var nST = nST.replace(/\+/g,'');
	setLSTItem = dojo.create('li', {
		innerHTML: 'ATCA Rise/Set LST: ' + nRT + ' / ' + nST });
    } else if (sourceSets.haset == -99.0) {
	setLSTItem = dojo.create('li', {
		innerHTML: 'ATCA Rise/Set LST: never rises' });
    } else if (sourceSets.haset == 99.0) {
	setLSTItem = dojo.create('li', {
		innerHTML: 'ATCA Rise/Set LST: never sets' });
    }
	    
    infoList.appendChild(setLSTItem);
    
    var raEls = data.ra.split(/\:/g);
    var decEls = data.dec.split(/\:/g);
    var link = [];
    var linkHREFprefix = 'http://skyview.gsfc.nasa.gov/cgi-bin/nnskcall.pl?' +
	'Interface=bform&VCOORD=' + raEls[0] + '+' + raEls[1] +
	'+' + raEls[2] + '%2C+' + decEls[0] + '+' + decEls[1] +
	'+' + decEls[2];
    var linkHREFsuffix = '&SCOORD=Equatorial&EQUINX=2000&' +
	'MAPROJ=Gnomic&SFACTR=0.3&' +
	'ISCALN=Linear&GRIDD=No';

    // check for PMN image
    if (((decEls[0] > -88) && (decEls[0] <= -39)) ||
	((decEls[0] > -27) && (decEls[0] <= -9)) ||
	((decEls[0] >= 0) && (decEls[0] <= 77))) {
	link.push(dojo.create('a', {
		    target: 'skyview',
		    href: linkHREFprefix + '&SURVEY=GB6+%284850Mhz%29' +
			linkHREFsuffix,
			innerHTML: 'PMN/GB6 image' }));
    }

    // check for SUMSS image
    if (decEls[0] < -30) {
	link.push(dojo.create('a', {
		    target: 'skyview',
		    href: linkHREFprefix + '&SURVEY=SUMSS+843+MHz' +
			linkHREFsuffix,
			innerHTML: 'SUMSS image' }));
    }

    // check for NVSS image
    if (decEls[0] > -39) {
	link.push(dojo.create('a', {
		    target: 'skyview',
		    href: linkHREFprefix + '&SURVEY=NVSS' +
			linkHREFsuffix,
			innerHTML: 'NVSS image' }));
    }

    // NED link
    link.push(dojo.create('a', {
		target: 'ned',
		    href: 'http://nedwww.ipac.caltech.edu/cgi-bin/' +
		    'nph-objsearch?search_type=Near+Position+Search&' +
		    'in_csys=Equatorial&in_equinox=J2000.0&lon=' +
		    raEls[0] + '+' + raEls[1] + '+' + raEls[2] +
		    '&lat=' + decEls[0] + '+' + decEls[1] + '+' +
		    decEls[2] + '&radius=5.0&out_csys=Equatorial&' +
		    'out_equinox=J2000.0&obj_sort=Distance+to+search+' +
		    'center&zv_breaker=30000.0&list_limit=5&' +
		    'img_stamp=YES&z_constraint=Unconstrained&' +
		    'z_value1=&z_value2=&z_unit=z&ot_include=ALL&' +
		    'in_objtypes2=Radio',
		    innerHTML: 'NED reference' }));
    
    for (var i = 0; i < link.length; i++) {
	var linkItem = dojo.create('li');
	infoList.appendChild(linkItem);
	linkItem.appendChild(link[i]);
    }

    // information about the calibrator catalogue
    var calInfoDiv = dojo.create('div', {
	    'class': 'calInfo' });
    DOMnode.appendChild(calInfoDiv);
    calInfoDiv.appendChild(dojo.create('h2', {
		innerHTML: 'Calibrator Information' }));
    calInfoDiv.appendChild(dojo.create('b', {
		innerHTML: 'Catalogue: ' + data.catalogue }));
    calInfoDiv.appendChild(dojo.create('br'));
    calInfoDiv.appendChild(dojo.create('br'));

    // what is the positional quality?
    var posSpan = dojo.create('span');
    calInfoDiv.appendChild(posSpan);
    var posText = 'Positional uncertainty and reference:&nbsp;&nbsp;';
    if (data.quality){
	posText += data.quality;
    } else {
	posText += 'Unknown';
    }
    posSpan.innerHTML = posText;

    DOMnode.appendChild(dojo.create('hr'));

    // any observer notes?
    if (data.notes != '') {
	DOMnode.appendChild(dojo.create('h2', {
		    innerHTML: 'Notes' }));
	DOMnode.appendChild(dojo.create('div', {
		    innerHTML: data.notes }));
	DOMnode.appendChild(dojo.create('hr'));
    }


    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('br'));

    // observations
    DOMnode.appendChild(dojo.create('span', {
		'class': 'recommendedFreq',
		    innerHTML: 'Entries with this background colour ' +
		    'represent the recommended continuum frequencies ' +
		    'for use with CABB.' }));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('span', {
		innerHTML: 'Click on the blue bar labelled with the ' +
		    'band name to show or hide the measurements for ' +
		    'that band.' }));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('br'));
    
    var obsDiv = dojo.create('div', {
	    id: 'observationSummary' });
    DOMnode.appendChild(obsDiv);
    insertLoadingImage(obsDiv);

    // the defect text
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('span', {
		innerHTML: 'The defect is an estimate of percentage of ' +
		    '(coherent) emission in the field in addition to the ' +
		    'point source component. Because of resolution ' +
		    'effects, it is dependent on array size.' }));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('span', {
		innerHTML: 'The quoted uncertainty on the flux densities ' +
		    'is indicative of the signal-to-noise ratio of the ' +
		    'observation from which the measurement was derived. ' +
		    'It should not be considered indicative of the quality ' +
		    'of the flux calibration.' }));
    DOMnode.appendChild(dojo.create('br'));
    DOMnode.appendChild(dojo.create('br'));


    // VLA info
    if (data.vla_text) {
	DOMnode.appendChild(dojo.create('hr'));
	DOMnode.appendChild(dojo.create('br'));
	var vlaLeftDiv = dojo.create('div', {
		'class': 'vlaInfo' });
	DOMnode.appendChild(vlaLeftDiv);

	vlaLeftDiv.appendChild(dojo.create('h2', {
		    innerHTML: 'VLA calibrator manual information' }));
	var vla_pretext = data.vla_text.replace(/\<br \/\>/g,'\n');
	vlaLeftDiv.appendChild(dojo.create('pre', {
		    innerHTML: vla_pretext }));

	var vlaRightDiv = dojo.create('div', {
		'class': 'vlaKey' });
	DOMnode.appendChild(vlaRightDiv);
	vlaRightDiv.appendChild(dojo.create('h2', {
		    innerHTML: 'VLA calibrator manual key' }));
	vlaRightDiv.appendChild(dojo.create('span', {
		    innerHTML: 'The VLA calibrator tables present the ' +
			'information in a very concise (and thus ' +
			'somewhat cryptic) format.'}));
	vlaRightDiv.appendChild(dojo.create('br'));
	vlaRightDiv.appendChild(dojo.create('a', {
		    target: 'vla',
			href: 'http://www.aoc.nrao.edu/~gtaylor/' +
			'calman/key.html',
			innerHTML: 'A document describing the format' }));
	vlaRightDiv.appendChild(dojo.create('span', {
		    innerHTML: ' in detail is available at the VLA ' +
			'web site. Below is an abbreviated version.' }));
	vlaRightDiv.appendChild(dojo.create('br'));
	vlaRightDiv.appendChild(dojo.create('br'));
	vlaRightDiv.appendChild(dojo.create('b', {
		    innerHTML: 'Note:'}));
	vlaRightDiv.appendChild(dojo.create('span', {
		    innerHTML: ' The ATCA schedulers always use the ' +
			'B1950 names of calibrators, even though ' +
			'they use the J2000 coordinates.' }));
	vlaRightDiv.appendChild(dojo.create('br'));
	vlaRightDiv.appendChild(dojo.create('br'));
		    
	var keyImage = dojo.create('img', {
		src: 'http://www.narrabri.atnf.csiro.au/' +
		'calibrators/vla.gif' });
	vlaRightDiv.appendChild(keyImage);

	var vlaDiv = dojo.create('div', {
		'class': 'vlaBottom' });
	DOMnode.appendChild(vlaDiv);
	var vlaPara = dojo.create('p', {
		innerHTML: 'Reference: ' });
	vlaDiv.appendChild(vlaPara);
	vlaPara.appendChild(dojo.create('a', {
		    target: 'vla',
		    href: 'http://www.vla.nrao.edu/astro/calib/' +
			'manual/index.shtml',
			innerHTML: 'VLA calibrator manual.' }));
	vlaPara = dojo.create('p');
	vlaDiv.appendChild(vlaPara);
	vlaPara.appendChild(dojo.create('b', {
		    innerHTML: 'NOTE:' }));
	vlaPara.appendChild(dojo.create('span', {
		    innerHTML: 'The larger size of the ATCA primary beam ' +
			'compared with the VLA means that VLA ' +
			'calibrators can be affected by additional ' +
			'confusion when observed with the ATCA.' }));
    }

    // query the database for the observations
    dojo.xhrPost({
	    url: '/cgi-bin/Calibrators/new/calinfo_json.pl' ,
		sync: false,
		content: { action: 'measurements',
		    source: data.name },
		handleAs: 'json',
		load: drawCalViewMeasurements,
		error: function(error, ioargs){
		alert(error);
	    }
	});

    
}

function drawFluxHistoryPage(data, ioargs) {
    var DOMnode = dojo.byId('contentDiv');

    var plotDiv = dojo.create('div');
    DOMnode.appendChild(plotDiv);

    if (data && data.timeSeries) {
	// make the plot area
	var plotObject = new Highcharts.Chart({
		chart: { renderTo: plotDiv,
			 defaultSeriesType: 'scatter',
			 animation: false },
		plotOptions: { series: { animation: false  } },
		title: { text: '' },
		xAxis: { type: 'datetime',
			 title: { enabled: true, text: 'Time (UT)',
				  startOnTick: false, endOnTick: false,
				  showLastLabel: true }},
		yAxis: { title: { text: 'Flux density (Jy)' }},
		series: data.timeSeries
	    });
    }
}

function drawVisPlotPage(data, ioargs) {
    var DOMnode = dojo.byId('contentDiv');

    if ((!data) ||
	(!data.plots) ||
	((!data.plots.uvdist) && (!data.plots.ucvc))) {
	DOMnode.appendChild(dojo.create('h2', {
		    innerHTML: 'No plots found!' }));
	return;
    } else {
	if (data.plots.uvdist) {
	    var imageDiv = dojo.create('div', {
		    'class': 'imageDiv' });
	    DOMnode.appendChild(imageDiv);
	    
	    var uvdistImg = dojo.create('img', {
		    src: '/' + data.plots.uvdist,
		    'class': 'visplotImage' });
	    imageDiv.appendChild(uvdistImg);
	    imageDiv.appendChild(dojo.create('br'));
	    imageDiv.appendChild(dojo.create('span', {
			innerHTML: 'The function of visibility ' +
			    'amplitude with uv distance at ' +
			    options.band + '.' }));
	    imageDiv.appendChild(dojo.create('br'));
	    imageDiv.appendChild(dojo.create('span', {
			innerHTML: 'Each epoch and frequency is ' +
			    'plotted in a different colour (up to 12, ' +
			    'then reused).' }));
	    dojo.place('<br /><span>For help with interpreting these ' +
		       'plots, please click <a href=' +
		       '"http://www.narrabri.atnf.csiro.au/calibrators/' +
		       'newdb/choosing_calibrator.html#visibility_plots">' +
		       'here</a>.</span>', imageDiv);
	}

	if (data.plots.ucvc) {
	    var imageDiv = dojo.create('div', {
		    'class': 'imageDiv' });
	    DOMnode.appendChild(imageDiv);
	    
	    var uvdistImg = dojo.create('img', {
		    src: '/' + data.plots.ucvc,
		    'class': 'visplotImage' });
	    imageDiv.appendChild(uvdistImg);
	    imageDiv.appendChild(dojo.create('br'));
	    imageDiv.appendChild(dojo.create('span', {
			innerHTML: 'The uv-plane coverage of the ' +
			    'above uv distance plot.' }));
	    
	}

    }
	
}

function getOptions() {

    // get the arguments from the address bar
    var instructions = location.search + '&';

    var tokenNext = nextToken(instructions);
    while (tokenNext != null) {
	var response = tagValue({tag: tokenNext,
				 string: instructions});
	instructions = response.remainder;
	
	if (/\,/.test(response.value) == false){
	    // just a single value
	    options[tokenNext] = response.value;
	} else {
	    // a list of values, turn it into an array
	    options[tokenNext] =
		response.value.split(/\,/g);
	}
	tokenNext = nextToken(instructions);
    }

    return;
}

function insertLoadingImage(node) {
    var loadingDiv = dojo.create('div', {
	    style: 'margin: 0px auto;' });
    var loadingImage = dojo.create('img', {
	    src: 'loading.gif' });

    loadingDiv.appendChild(loadingImage);
    node.appendChild(loadingDiv);
    return;
}

function enableQueryPage() {
    
    // setup the submit buttons
    var newdb_nearButton = dojo.byId('near_newdb');
    var olddb_nearButton = dojo.byId('near_olddb');
    var nearRaDecEntry = dojo.byId('near_name');
    var nearThetaEntry = dojo.byId('near_theta');
    var nearFlimitEntry = dojo.byId('near_flimit');
    dojo.connect(newdb_nearButton, 'onclick', nearAction);
    dojo.connect(olddb_nearButton, 'onclick', nearAction);
    dojo.connect(nearRaDecEntry, 'onkeydown', nearAction);
    dojo.connect(nearThetaEntry, 'onkeydown', nearAction);
    dojo.connect(nearFlimitEntry, 'onkeydown', nearAction);

    // viewing the details for a named calibrator
    var newdb_viewButton = dojo.byId('view_newdb');
    dojo.connect(newdb_viewButton, 'onclick', viewAction);
    var olddb_viewButton = dojo.byId('view_olddb');
    dojo.connect(olddb_viewButton, 'onclick', viewAction);
    var viewSourceEntry = dojo.byId('view_source');
    dojo.connect(viewSourceEntry, 'onkeydown', viewAction);

    // getting a table of calibrators in an RA,Dec range
    var newdb_tableButton = dojo.byId('table_newdb');
    var olddb_tableButton = dojo.byId('table_olddb');
    var tableRARangeEntry = dojo.byId('table_rarange');
    var tableDecRangeEntry = dojo.byId('table_decrange');
    var tableFlimitEntry = dojo.byId('table_flimit');
    dojo.connect(newdb_tableButton, 'onclick', tableAction);
    dojo.connect(olddb_tableButton, 'onclick', tableAction);
    dojo.connect(tableRARangeEntry, 'onkeydown',
		 tableAction);
    dojo.connect(tableDecRangeEntry, 'onkeydown',
		 tableAction);
    dojo.connect(tableFlimitEntry, 'onkeydown',
		 tableAction);
}

function nearAction(evtObj) {
    var radecInput = dojo.byId('near_name');
    var radecOrigValue = radecInput.value;
    var radecValue = radecInput.value.replace(/\s/g, '');
    var thetaInput = dojo.byId('near_theta');
    var thetaValue = thetaInput.value.replace(/\s/g, '');

    if (((evtObj.target == dojo.byId('near_name')) ||
	 (evtObj.target == dojo.byId('near_theta')) ||
	 (evtObj.target == dojo.byId('near_flimit'))) &&
	(evtObj.keyCode != dojo.keys.ENTER)) {
	return;
    }
    
    if ((radecValue == '') || (thetaValue == '')) {
	return;
    }

    if ((evtObj.target == dojo.byId('near_name')) ||
	(evtObj.target == dojo.byId('near_theta')) ||
	(evtObj.target == dojo.byId('near_flimit')) ||
	(evtObj.target == dojo.byId('near_newdb'))) {
	if (evtObj.keyCode == dojo.keys.ENTER) {
	    dojo.stopEvent(evtObj);
	}
	options.callingdb = 'new';
    } else {
	options.callingdb = 'old';
    }
    
    // what was in the name field?
    if (/\,/.test(radecValue) == false) {
	dojo.xhrPost({
		url: '/cgi-bin/Calibrators/new/sourcequery.pl' ,
		    sync: false,
		    content: { name: radecValue },
		    handleAs: 'json',
		    load: gotName,
		    error: function(error, ioargs){
		    alert(error);
		}
	    });
    } else if (/\:/.test(radecValue) == false) {
	// in decimal
	var radecEls = radecValue.split(/\,/g);
	var sRA = d2s(radecEls[0]);
	var sDec = d2s(radecEls[1]);
	radecInput.value = sRA + ',' + sDec;
	nearQuery();
    } else {
	nearQuery();
    }

}

function d2s(dnumber) {
    var sign = 1;
    if (dnumber<0) {
	sign = -1;
    }
    dnumber *= sign;

    var dD = Math.floor(dnumber);
    dnumber -= dD;
    dnumber *= 60;
    var dM = Math.floor(dnumber);
    dnumber -= dM;
    dnumber *= 60;
    var dS = Math.floor(dnumber*100)/100;
    var prefix = '';
    if (sign == -1) {
	prefix = '-';
    }

    if (dM < 10) {
	dM = '0' + dM;
    }
    if (dS < 10) {
	dS = '0' + dS;
    }

    return (prefix+ dD + ':' + dM + ':' + dS);
}

function gotName(data, ioargs) {
    if ((data.position) &&
	(data.position != '')) {
	dojo.byId('near_name').value = data.position;
	nearQuery();
    } else {
	return;
    }
}    
 
function nearQuery() {
    var radecInput = dojo.byId('near_name');
    var radecOrigValue = radecInput.value;
    var radecValue = radecInput.value.replace(/\s/g, '');
    var thetaInput = dojo.byId('near_theta');
    var thetaValue = thetaInput.value.replace(/\s/g, '');
    var flimitInput = dojo.byId('near_flimit');
    var flimitValue = flimitInput.value.replace(/\s/g, '');

    var bandNames = ['20cm', '13cm', '6cm', '3cm', '15mm',
		     '7mm', '3mm'];
    var bandSelected = '';
    for (var i = 0; i < bandNames.length; i++) {
	if (dojo.byId('near_flimit_' + bandNames[i]).
	    checked == true) {
	    bandSelected = bandNames[i];
	}
    }
    var location = 'http://www.narrabri.atnf.csiro.au/';
    if (options.callingdb == 'new') {
	if ((bandSelected == '20cm') ||
	    (bandSelected == '13cm')) {
	    bandSelected = '16cm';
	}
	location +=
	    'calibrators/newdb/caltable.html?radec=' +
	    radecValue + '&theta=' +
	    thetaValue + '&flimit=' +
	    flimitValue + '&band=' + bandSelected;
	window.location.href = location;
    } else if (options.callingdb == 'old') {
	if (bandSelected == '15mm') {
	    bandSelected = '12mm';
	}
	location +=
	    'cgi-bin/Calibrators/calnear.cgi?radec=' +
	    radecValue + '&theta=' +
	    thetaValue + '&flimit=' +
	    flimitValue + '&band=' + bandSelected;
	window.location.href = location;
    }
}

function tableAction(evtObj) {
    var rarangeInput = dojo.byId('table_rarange');
    var rarangeValue = rarangeInput.value.replace(/\s/g, '');
    var decrangeInput = dojo.byId('table_decrange');
    var decrangeValue = decrangeInput.value.replace(/\s/g, '');
    
    if ((rarangeValue == '') || (decrangeValue == '') ||
	(/\,/.test(rarangeValue) == false) ||
	(/\,/.test(decrangeValue) == false)) {
	return;
    }

    var flimitInput = dojo.byId('table_flimit');
    var flimitValue = flimitInput.value.replace(/\s/g, '');
    
    var bandNames = ['20cm', '13cm', '6cm', '3cm', '15mm',
		     '7mm', '3mm'];
    var bandSelected = '';
    for (var i = 0; i < bandNames.length; i++) {
	if (dojo.byId('table_flimit_' + bandNames[i]).
	    checked == true) {
	    bandSelected = bandNames[i];
	}
    }
    var location = 'http://www.narrabri.atnf.csiro.au/';
    if ((evtObj.target == dojo.byId('table_newdb')) ||
	(evtObj.keyCode == dojo.keys.ENTER)) {
	dojo.stopEvent(evtObj);
	if ((bandSelected == '20cm') ||
	    (bandSelected == '13cm')) {
	    bandSelected = '16cm';
	}

	location +=
	    'calibrators/newdb/caltable.html?rarange=' +
	    rarangeValue + '&decrange=' +
	    decrangeValue + '&flimit=' +
	    flimitValue + '&band=' + bandSelected;
	window.location.href = location;
    } else if (evtObj.target == dojo.byId('table_olddb')) {
	if (bandSelected == '15mm'){
	    bandSelected = '12mm';
	}

	location += 
	    'cgi-bin/Calibrators/caltable.cgi?rarange=' +
	    rarangeValue + '&decrange=' +
	    decrangeValue + '&flimit=' +
	    flimitValue + '&band=' + bandSelected;
	window.location.href = location;
    }
    
}

function viewAction(evtObj) {
    var sourceInput = dojo.byId('view_source');
    var sourceName = sourceInput.value.replace(/\s/g, '');

    if (sourceName == '') {
	return;
    }
    var location = 'http://www.narrabri.atnf.csiro.au/';
    if ((evtObj.target == dojo.byId('view_newdb')) ||
	(evtObj.keyCode == dojo.keys.ENTER)) {
	dojo.stopEvent(evtObj);
	location += 
	    'calibrators/newdb/calview.html?source=' +
	    sourceName;
	window.location.href = location;
    } else if (evtObj.target == dojo.byId('view_olddb')) {
	location +=
	    'cgi-bin/Calibrators/calview.cgi?source=' +
	    sourceName + '&mode=cals';
	window.location.href = location;
    }
}

function AstroCoord(options) {
  this.lst = 0.0;

  this.sources = [];

  // some known locations
  this.locations = [
    {
      telescope: 'ATCA', longitude: 149.550138889, latitude: -30.312884722,
      elevation_low: 12.0
    }
  ];
  this.setLocation();

}

AstroCoord.prototype.setLocation = function(options) {
  if (!options) {
    // default to the ATCA
    options = {
      telescope: 'ATCA'
    };
  }
  if (options.telescope && options.longitude && options.latitude &&
      options.elevation_low) {
    // we got all the info passed to us
    this.telescope = options.telescope;
    this.longitude = options.longitude;
    this.latitude = options.latitude;
    this.elevation_low = options.elevation_low;
  } else {
    // find the location
    for (var i = 0; i < this.locations.length; i++) {
      if (options.telescope == this.locations[i].telescope) {
	this.telescope = this.locations[i].telescope;
	this.longitude = this.locations[i].longitude;
	this.latitude = this.locations[i].latitude;
	this.elevation_low = this.locations[i].elevation_low;
      }
    }
  }
};

AstroCoord.prototype.removeSource = function(source) {
  if (!source) {
    return;
  }
  if (!source.name) {
    return;
  }
  // find the source
  for (var i = 0; i < this.sources.length; i++) {
    if (this.sources[i].name == source.name) {
      this.sources.splice(i,1);
      break;
    }
  }
};

AstroCoord.prototype.addSource = function(source) {
  // check the source we were passed and add the information
  // that wasn't included
  if (!source) {
    return;
  }
  if (!source.name) {
    return;
  }
  if (!source.ra && !source.ra_turns) {
    return;
  }
  if (!source.dec && !source.dec_turns) {
    return;
  }
  if (source.ra && !source.ra_turns) {
    // calculate the RA in turns
    source.ra_turns = this.hexa2turns(source.ra, 'hours');
  }
  if (source.dec && !source.dec_turns) {
    // calculate the Dec in turns
    source.dec_turns = this.hexa2turns(source.dec, 'decimal');
  }
  // check that the source isn't already on our list
  var added = false;
  for (var i = 0; i < this.sources.length; i++) {
    if (this.sources[i].name == source.name) {
      added = true;
      break;
    }
  }
  if (added == false) {
    this.sources.push(source);
    // calculate some stuff for this source
    this.calc_haset_azel(source.name);
  }
};

AstroCoord.prototype.calc_positions = function() {
  // calculate the current az/els for all the sources
  // this requires the hour angle to be calculated
  this.calc_times();
  for (var i = 0; i < this.sources.length; i++) {
    var resp = this.eqazel({ az: this.sources[i].hourAngle,
    el: this.sources[i].dec_turns,
    latitude: this.deg2turn(this.latitude) });
    this.sources[i].current_az = resp.ha;
    this.sources[i].current_el = resp.dec;
  }
};

AstroCoord.prototype.eqazel = function(params) {
  // from CJP's Astro::Coord Perl module
  // params has elements az, el, latitude, allownegative
  var sphi = Math.sin(this.turn2rad(params.latitude));
  var cphi = Math.cos(this.turn2rad(params.latitude));
  var sleft = Math.sin(this.turn2rad(params.az));
  var cleft = Math.cos(this.turn2rad(params.az));
  var sright = Math.sin(this.turn2rad(params.el));
  var cright = Math.cos(this.turn2rad(params.el));
  var left_out = Math.atan2(-1 * sleft,-1 * cleft * sphi +
    sright * cphi/cright)/(2 * Math.PI);
  if (params.allownegative && left_out < 0) {
    left_out += 1.0;
  }
  var right_out = Math.asin(cleft * cright * cphi + sright * sphi)/
    (2 * Math.PI);

  return ({ ha: left_out, dec: right_out });
};

AstroCoord.prototype.deg2turn = function(degval) {
  return (degval / 360.0);
};

AstroCoord.prototype.turn2rad = function(turnval) {
  // from CJP's Astro::Time Perl module
  return (turnval * 2 * Math.PI);
};

AstroCoord.prototype.calc_times = function() {
  // calculate the rise and set times for all the sources
  for (var i = 0; i < this.sources.length; i++) {
    var hourAngle1 = this.lst - this.sources[i].ra_turns;
    var hourAngle2 = this.lst + 1 - this.sources[i].ra_turns;
    var hourAngle;
    if (Math.abs(hourAngle1) < Math.abs(hourAngle2)){
      hourAngle = hourAngle1;
    } else {
      hourAngle = hourAngle2;
    }
    if (hourAngle > 0.5) {
      hourAngle -= 1.0;
    } else if (hourAngle < -0.5) {
      hourAngle = 1 - hourAngle;
    }
    this.sources[i].hourAngle = hourAngle;
    if (this.sources[i].haset == -99.0) {
      this.sources[i].actionName = 'Rise time';
      this.sources[i].actionTime = 'never rises';
    } else if (this.sources[i].haset == 99.0) {
      this.sources[i].actionName = 'Set time';
      this.sources[i].actionTime = 'never sets';
    } else {
      if (this.sources[i].haset > Math.abs(hourAngle)) {
	this.sources[i].actionName = 'Set time';
	var setTime = this.sources[i].haset - hourAngle;
	this.sources[i].actionTime = this.turns2str(setTime, 'hours', 'time');
      } else {
	this.sources[i].actionName = 'Rise time';
	var riseTime = (-1 * this.sources[i].haset) - hourAngle;
	if (riseTime < 0.0) {
	  riseTime += 1.0;
	}
	this.sources[i].actionTime = this.turns2str(riseTime, 'hours', 'time');
      }
    }
  }
};

AstroCoord.prototype.turns2str = function(turns, inType, outType) {
  var value = turns;
  if (inType == 'hours') {
    value *= 24.0;
  } else if (inType == 'degrees') {
    value *= 360.0;
  }
  var sign = 1;
  if (value < 0.0) {
    sign = -1;
  }
  value *= sign;

  if (outType == 'coordinateHours') {
    value /= 15.0;
  }

  var origValue = sign * value;
  var field1 = Math.floor(value);
  value -= field1;
  value *= 60.0;
  var field2 = Math.floor(value);
  value -= field2;
  value *= 60.0;
  var field3 = value;

  // field1 *= sign;
  var format = '';
  var valueString;
  if (outType == 'time') {
    if (sign == -1) {
      format = '-';
    }
    format += "%'02d:%'02d:%'02d";
    valueString = sprintf(format, field1, field2, field3);
  } else if ((outType == 'coordinateHours') ||
	     (outType == 'coordinateDegrees')) {
    format = "%'02d:%'02d:%'05.2f";
    valueString = sprintf(format, field1, field2, field3);
    if (sign < 0) {
      valueString = '-' + valueString;
    } else {
      valueString = '+' + valueString;
    }
  } else if (outType == 'decimalDegrees') {
    format = "%+'7.3f";
    valueString = sprintf(format, origValue);
  }

  return valueString;
};

AstroCoord.prototype.hexa2turns = function(hexa_string, type) {

  var hexa_split = hexa_string.split(/[\s\:]/g);
  var sign = 1;
  if (/^\-/.test(hexa_string)){
    sign = -1;
  }
  if (sign == -1) {
    hexa_split[0] = hexa_split[0].replace(/^\-/, '');
  }
  if (hexa_split[0] != '0') {
    hexa_split[0] = hexa_split[0].replace(/^0/, '');
  }
  if (hexa_split[1] != '0') {
    hexa_split[1] = hexa_split[1].replace(/^0/, '');
  }
  if (hexa_split[2] != '0') {
    hexa_split[2] = hexa_split[2].replace(/^0/, '');
  }
  var decimal = parseInt(hexa_split[0]); // * sign;
  decimal += parseInt(hexa_split[1])/60.0;
  decimal += parseFloat(hexa_split[2])/3600.0;
  decimal *= sign;

  if (type == 'hours') {
    decimal *= 15.0;
  }

  decimal /= 360.0;

  return (decimal);
};

AstroCoord.prototype.calc_haset_azel = function(sourceName) {
  // go through the sources
  if (!sourceName) {
    sourceName = 'all'; // default to calculating this for all sources
  }
  for (var i = 0; i < this.sources.length; i++) {
    if ((this.sources[i].name == sourceName) || (sourceName == 'all')) {
      this.sources[i].haset = this.haset_azel(this.sources[i].dec_turns);
    }
  }
  return;
};

AstroCoord.prototype.haset_azel = function(dec) {
  var cos_haset = (Math.cos(Math.PI/2.0 - this.elevation_low/360.0 * 2.0 *
    Math.PI) - Math.sin(this.latitude/360.0 * 2.0 * Math.PI) *
    Math.sin(dec * 2.0 * Math.PI))/(Math.cos(dec * 2.0 * Math.PI) *
    Math.cos(this.latitude/360.0 * 2.0 * Math.PI));
  if (cos_haset > 1.0) {
    // source never rises
    return -99.0;
  } else if (cos_haset < -1.0) {
    // source never sets
    return 99.0;
  } else {
    return (Math.acos(cos_haset)/(2.0 * Math.PI));
  }
};

AstroCoord.prototype.get_info = function(sourceName) {
  for (var i = 0; i < this.sources.length; i++) {
    if (this.sources[i].name == sourceName) {
      return this.sources[i];
    }
  }
  return null;
};
